package cn.touna.jss.common.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.StringUtils;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * http、https 请求工具类， 微信为https的请求
 *
 * @author yehx
 */
public class HttpUtil {

    private final static Logger logger = LogManager.getLogger();

    private static final String DEFAULT_CHARSET = "UTF-8";

    private static final String _GET = "GET"; // GET
    private static final String _POST = "POST";// POST
    public static final int DEF_CONN_TIMEOUT = 30000;
    public static final int DEF_READ_TIMEOUT = 30000;

    public static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";
    public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";

    /**
     * 初始化http请求参数
     *
     * @param url
     * @param method
     * @param headers
     * @return
     * @throws Exception
     */
    private static HttpURLConnection initHttp(String url, String method, Map<String, String> headers) throws Exception {
        URL _url = new URL(url);
        HttpURLConnection http = (HttpURLConnection) _url.openConnection();
        // 连接超时
        http.setConnectTimeout(DEF_CONN_TIMEOUT);
        // 读取超时 --服务器响应比较慢，增大时间
        http.setReadTimeout(DEF_READ_TIMEOUT);
        http.setUseCaches(false);
        http.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded");
        http.setRequestProperty(
                "User-Agent",
                "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
        if (null != headers && !headers.isEmpty()) {
            for (Entry<String, String> entry : headers.entrySet()) {
                http.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }
        http.setDoOutput(true);
        http.setDoInput(true);
        http.setRequestMethod(method);
        http.connect();
        return http;
    }

    /**
     * 初始化http请求参数
     *
     * @param url
     * @param method
     * @return
     * @throws Exception
     */
    private static HttpsURLConnection initHttps(String url, String method,
                                                Map<String, String> headers, KeyStore keyStore, String keyPwd) throws Exception {
        KeyManager[] keyManagers = null;
        if (keyStore != null) {
            keyManagers = getKeyManagers(keyStore, keyPwd);
        }
        SSLSocketFactory ssf = getSslSocketFactory(keyManagers);
        URL _url = new URL(url);
        HttpsURLConnection http = (HttpsURLConnection) _url.openConnection();
        // 设置域名校验
        http.setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
        // 连接超时
        http.setConnectTimeout(DEF_CONN_TIMEOUT);
        // 读取超时 --服务器响应比较慢，增大时间
        http.setReadTimeout(DEF_READ_TIMEOUT);
        http.setUseCaches(false);
        http.setRequestMethod(method);
        http.setRequestProperty(
                "User-Agent",
                "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
        if (null != headers && !headers.isEmpty()) {
            for (Entry<String, String> entry : headers.entrySet()) {
                http.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }
        http.setSSLSocketFactory(ssf);
        http.setDoOutput(true);
        http.setDoInput(true);
        http.connect();
        return http;
    }

    private static SSLSocketFactory getSslSocketFactory(KeyManager[] keyManagers) throws NoSuchAlgorithmException, KeyManagementException {
        TrustManager[] tm = new X509TrustManager[]{new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }};
        System.setProperty("https.protocols", "TLSv1.2");
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagers, tm, new java.security.SecureRandom());
        // 从上述SSLContext对象中得到SSLSocketFactory对象
        return sslContext.getSocketFactory();
    }

    public static KeyManager[] getKeyManagers(KeyStore keyStore, String keyPwd) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
        //初始化 Key Manager Factory
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, keyPwd.toCharArray());
        return kmf.getKeyManagers();
    }

    public static KeyStore getKeyStore(InputStream keyStoreInput, String keyPwd) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        // 本地加载KeyStore
        if (keyStoreInput == null)
            return null;
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(keyStoreInput, keyPwd.toCharArray());
        keyStoreInput.close();
        return keyStore;
    }

    //generic
    public static String get(String url, Map<String, String> params, Map<String, String> headers, String body) throws Exception {
        HttpURLConnection http = null;
        if (isHttps(url)) {
            http = initHttps(initParams(url, params), _GET, headers, null, null);
        } else {
            http = initHttp(initParams(url, params), _GET, headers);
        }
        OutputStream out = null;
        if (!StringUtils.isEmpty(body)) {
            out = http.getOutputStream();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
            bw.append(body);
            bw.flush();
        }
        StringBuffer bufferRes = new StringBuffer();
        InputStream in = http.getInputStream();
        try {
            BufferedReader read = new BufferedReader(new InputStreamReader(in,
                    DEFAULT_CHARSET));
            String valueString = null;
            while ((valueString = read.readLine()) != null) {
                bufferRes.append(valueString);
            }
        } finally {
            if (out != null) {
                out.close();
            }
            if (in != null)
                in.close();
            if (http != null)
                http.disconnect();
        }
        return bufferRes.toString();
    }


    public static String get(String url) throws Exception {
        return get(url, null);
    }

    public static String get(String url, Map<String, String> params) throws Exception {
        return get(url, params, null);
    }

    /**
     * @return 返回类型:
     * @throws Exception
     * @description 功能描述: get 请求
     */
    public static String get(String url, Map<String, String> params, Map<String, String> headers) throws Exception {
        return get(url, params, headers, null);
    }


    /**
     * post,json字符写入流
     *
     * @param urlStr
     * @param content
     * @return
     * @throws Exception
     */
    public static String postJson(String urlStr, JSONObject content) throws Exception {
        return post(urlStr, content.toJSONString(), CONTENT_TYPE_JSON);
    }

    /**
     * post 表单
     *
     * @param urlStr
     * @param map
     * @return
     * @throws Exception
     */
    public static String postForm(String urlStr, Map<String, String> map) throws Exception {
        return post(urlStr, map2Url(map), CONTENT_TYPE_FORM);
    }

    /**
     * post<br/>
     * contentType：json格式(application/json;charset=UTF-8),表单模式(application/x-www-form-urlencoded)<br/>
     * 当为表单模式时，需要传入json字符串；当为表单模式时，需要传入queryString字符传<br/>
     * 不支持：表单中传递文件
     *
     * @param urlStr      url地址
     * @param content     内容：
     * @param contentType 内容格式：
     * @return
     * @throws Exception
     */
    public static String post(String urlStr, String content, String contentType) throws Exception {
        return post(urlStr, content, contentType, null, null);
    }

    public static String post(String urlStr, String content, String contentType, KeyStore keyStore, String keyPwd) throws Exception {
        return post(urlStr, content, contentType, null, keyStore, keyPwd);
    }

    public static String post(String urlStr, String content, String contentType, Map<String, String> headerMap, KeyStore keyStore, String keyPwd) throws Exception {
        HttpURLConnection conn = null;
        BufferedWriter bw = null;
        BufferedReader br = null;
        try {
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Type", contentType);
            if (headerMap != null)
                headers.putAll(headerMap);
            if (isHttps(urlStr)) {
                conn = initHttps(urlStr, _POST, headers, keyStore, keyPwd);
            } else {
                conn = initHttp(urlStr, _POST, headers);
            }
            bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
            bw.write(content);
            bw.flush();
            bw.close();
            br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = null;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            bw.close();
            conn.disconnect();
            return sb.toString();
        } catch (Exception e) {
            logger.error("", e);
            throw e;
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null)
                conn.disconnect();
        }
    }

    public static String initParams(String url, Map<String, String> params) throws Exception {
        return initParams(url, params, DEFAULT_CHARSET);
    }

    /**
     * 功能描述: 构造请求参数
     *
     * @return 返回类型:
     * @throws Exception
     */
    public static String initParams(String url, Map<String, String> params, String encode)
            throws Exception {
        if (null == params || params.isEmpty()) {
            return url;
        }
        StringBuilder sb = new StringBuilder(url);
        if (url.indexOf("?") == -1) {
            sb.append("?");
        }
        sb.append(map2Url(params, encode));
        return sb.toString();
    }

    public static String map2Url(Map<String, String> paramToMap) throws Exception {
        return map2Url(paramToMap, DEFAULT_CHARSET);
    }

    /**
     * map构造QueryString
     *
     * @return 返回类型:
     * @throws Exception
     */
    public static String map2Url(Map<String, String> paramToMap, String encode)
            throws Exception {
        if (null == paramToMap || paramToMap.isEmpty()) {
            return null;
        }
        StringBuffer url = new StringBuffer();
        boolean isfist = true;
        for (Entry<String, String> entry : paramToMap.entrySet()) {
            if (isfist) {
                isfist = false;
            } else {
                url.append("&");
            }
            url.append(entry.getKey()).append("=");
            String value = entry.getValue();
            if (!StringUtils.isEmpty(value)) {
                if (!StringUtils.isEmpty(encode))
                    url.append(URLEncoder.encode(value, encode));
                else
                    url.append(value);
            }
        }
        return url.toString();
    }

    /**
     * 检测是否https
     *
     * @param url
     */
    private static boolean isHttps(String url) {
        return url.startsWith("https");
    }
}